package com.example.demo.controller;

import com.example.demo.Util.FruitAnnoUtil;
import com.example.demo.annotation.MyAnnotation;
import com.example.demo.dao.Fruit;
import com.example.demo.impl.TestBean;
import com.example.demo.service.testBean.TestBeanExtend;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 注解与反射
 */
@RestController
@RequestMapping("/api")
public class AnnotationController {

    private final ApplicationContext applicationContext;

    public AnnotationController(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 注解本质是一个继承了Annotation 的特殊接口，其具体实现类是Java 运行时生成的动态代理类。
     * 而我们通过反射获取注解时，返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解（接口）的方法，
     * 会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。
     * 而memberValues 的来源是Java 常量池。
     */

    /**
     * 注解的作用
     * 不是程序本身，可以对程序作出解释
     * 可以被其他程序（比如：解释器等）读取
     *
     * 元注解  解释其他注解的注解
     * @Target  用于描述注解的使用范围(被描述的注解可以用在什么地方 class 方法什么)
     *          @Target(value = ElementType.METHOD)
     *
     * @Retention  表示需要再什么级别保存注解信息，用于描述注解的生命周期
     *              source < class < runtime
     *              @Retention(value = RetentionPolicy.RUNTIME)
     *
     * @Document   说明该注解将被包含在javadoc中
     * @Inherited  说明子类可以继承父类中的该注解
     *
     * 自定义注解 @interface 用来声明一个注解， 自动继承 java.lang.annotation.Annotation 接口
     *  分析
     *      @interface 声明注解，格式 public @interface 注解名
     *      其中的每一个方法实际是声明了一个配置参数
     *      方法的名称就是参数的名称
     *      返回值类型就是参数的类型（返回值只能是基本类型 Class，String，enmu）
     *      可以通过default来声明参数的默认值
     *      如果只有一个参数成员，一般参数名为value
     *      注解元素必须要有值，我们定义的注解元素时，经常使用空字符串，0作为默认值
     * 参数
     *     String name() default "";
     *     default为默认值，如果没有默认值 注解使用时必须传值
     *
     * 反射机制（java是静态语言，反射） 通过一个class得到一个class对象
     *  反射是java被视为动态语言的关键，反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息，并能直接操作任意对象
     *  的内部属性及方法。
     *
     *  加载完类之后，在堆内存的方法区中就产生一个Class类型对象（一个类只有一个Class对象）这个对象就包含了完整的类的结构信息。
     *  我们可以通过这个对象看到类的结构。这个对象就像一面镜子，透过这个镜子看到类的结构，所以，我们称之为：反射。
     *
     *  正常方法
     *      引入需要的包类的名称 -> 通过new实例化 -> 取得实例化对象
     *  反射方式
     *      实例化对象 -> getClass()方法 -> 得到完成的包类名称
     *
     *  反射
     *      优点：可以实现动态创建对象和编译，体现出很大的灵活性
     *      缺点：对性能有影响。使用反射基本上是一种解释操作，我们可以告诉JVM，我们希望做什么并且它满足我们的要求。这类操作总是慢与直接执行相同的操作。
     *
     * 获取Class对象
     *  1、实例化对象.getClass()  获取的是 包名 + 类名
     *            .getSimpleName 获取的是 类名
     *  2、Class<?> deptClass = Class.forName("com.example.demo.dto.Department");
     *  3、通过类型名.class
     *  4、基本内置类型的包装类都有一个Type属性
     *  Integer.Type
     *  5、获取父类
     *  子类.getSuperclass()
     *
     *  invoke 调用反射的方法
     *      方法名.invoke(对象, 参数)
     *
     * 通过反射获取泛型
     *      method1.getGenericParameterTypes();
     *
     * 通过反射获取注解
     *        method1.getAnnotation(MyAnnotation.class);
     *      通过.属性名  获取注解中属性
     *
     * 反射优化
     *      method,filed, constructor 都有 setAccessible 方法
     *      作用是启动和禁用访问安全检查的开关
     *      参数为true 反射的对象在使用时取消访问检查，提高反射的效率。
     *      false则表示反射的对象执行访问检查
     */

    // 注解
    @MyAnnotation
    public void testMyAnnotation() {}

    // 获取注解信息
    @GetMapping("/anno")
    public void testAnno() {
        FruitAnnoUtil.fruitAnnoUtil(Fruit.class);
    }

    // 反射
    @GetMapping("/reflect")
    public void testReflection() throws Exception {
        Class<?> deptClass = Class.forName("com.example.demo.dto.Department");
        Object o = deptClass.newInstance();
        Class<?> serviceClass = Class.forName("com.example.demo.service.ExecutorTaskService");
        // 获取所有方法
        Method taskCallB = serviceClass.getMethod("taskCallB");
        Method[] methods = serviceClass.getMethods();
        // 获取当前类声明的方法
        Method[] currentMethods = serviceClass.getDeclaredMethods();
        for (Method method1 : methods) {
            method1.invoke(o, null);
            System.out.println(method1);
        }
    }


    /**
     * @Component 注入Bean中
     * Java设计模式之-利用applicationContext.getBeansOfType动态加载类
     */
    // 测试bean
    @GetMapping("/testBean")
    public void testBean() {
        Map<String, TestBean> beansOfType = applicationContext.getBeansOfType(TestBean.class);
        Map<String, TestBeanExtend> beanExtendMap = applicationContext.getBeansOfType(TestBeanExtend.class);

        System.out.println(beansOfType);
        System.out.println(beanExtendMap);
    }
}
